#include "sobjc-commons/defines.h"
#include "sobjc-commons/str_list.h"
#include "sobjc-commons/str_stream.h"

#define DEFAULT_SIZE    20

struct _objc_str_stream {
  char *s;
  size_t size;
  size_t s_size;
};

objc_str_stream_t
objc_make_str_stream()
{
  objc_str_stream_t st;

  st = ALLOC(struct _objc_str_stream);
  st->s = ALLOC_ATOM_N(char, DEFAULT_SIZE);
  st->s[0] = 0;
  st->size = DEFAULT_SIZE;
  st->s_size = 0;

  return st;
}

void
objc_free_str_stream(objc_str_stream_t self)
{
  objc_free(self->s);
  objc_free(self);
}

char *
objc_str_stream_value(objc_str_stream_t self)
{
  return self->s;
}

char *
objc_str_stream_copy_value(objc_str_stream_t self)
{
  return objc_strdup(self->s);
}

char *
objc_str_stream_copy_value_and_free(objc_str_stream_t self)
{
  char *s;

  s = objc_str_stream_copy_value(self);
  objc_free_str_stream(self);

  return s;
}

void
objc_str_stream_write(objc_str_stream_t self, const char *s)
{
  size_t len;

  len = strlen(s);
  if (self->size <= self->s_size + len) {
    self->size = (self->size + len) * 2;
    REALLOC_N(self->s, char, self->size);
  }
  strcpy(self->s + self->s_size, s);
  self->s_size += len;
}

void
objc_str_stream_writec(objc_str_stream_t self, char c)
{
  if (self->size <= self->s_size + 1) {
    self->size = (self->size + 1) * 2;
    REALLOC_N(self->s, char, self->size);
  }
  self->s[self->s_size++] = c;
  self->s[self->s_size] = 0;
}

void
objc_str_stream_writef(objc_str_stream_t self, const char *f, ...)
{
  va_list ap;

  va_start(ap, f);
  objc_str_stream_writefv(self, f, ap);
  va_end(ap);
}

void
objc_str_stream_writefv(objc_str_stream_t self, const char *f, va_list ap)
{
  const char *begin;
  char s[64], *temp = NULL, *ss = NULL, *p;
  size_t len, temp_len = 0, need_len;
  int width, prec;
  BOOL left = NO, is_long = NO, is_size_t = NO;

  for (; *f; f++) {
    if (*f == '%') {
      begin = f;
      width = -1;
      prec = -1;

      if (*f == '-') {
        left = YES;
        f++;
      }

      if (isdigit(*f))
        width = 0;
      while (isdigit(*f))
        width = (width * 10) + *f++ - '0';

      if (*f == '.') {
        f++;
        prec = 0;
        while (isdigit(*f))
          prec = (prec * 10) + *f++ - '0';
      }

      if (*f == 'l') {
        is_long = YES;
        f++;
      } else if (*f == 'z') {
        is_size_t = YES;
        f++;
      }

      need_len = f - begin;
      if (!temp) {
        temp_len = need_len + 1;
        temp = ALLOC_ATOM_N(char, temp_len);
      } else if (need_len > temp_len)
        REALLOC_N(temp, char, need_len + 1);
      strncpy(temp, begin, need_len);
      temp[need_len] = 0;

      switch (*f) {
      case 'c':
        sprintf(s, temp, va_arg(ap, int));
        break;
      case 'd':
        if (is_long)
          sprintf(s, temp, va_arg(ap, long));
        else if (is_size_t)
          sprintf(s, temp, (int)va_arg(ap, size_t));
        else
          sprintf(s, temp, va_arg(ap, int));
        break;
      case 'u':
        if (is_long)
          sprintf(s, temp, va_arg(ap, unsigned long));
        else if (is_size_t)
          sprintf(s, temp, va_arg(ap, size_t));
        else
          sprintf(s, temp, va_arg(ap, unsigned int));
        objc_str_stream_write(self, s);
        break;
      case 'i':
        sprintf(s, temp, va_arg(ap, int));
        objc_str_stream_write(self, s);
        break;
      case 'o':
        sprintf(s, temp, va_arg(ap, int));
        objc_str_stream_write(self, s);
        break;
      case 'x':
        sprintf(s, temp, va_arg(ap, int));
        objc_str_stream_write(self, s);
        break;
      case 'X':
        sprintf(s, temp, va_arg(ap, int));
        objc_str_stream_write(self, s);
        break;
      case 's':
        p = va_arg(ap, char *);
        if (width >= 0 || prec >= 0) {
          len = strlen(p);
          if ((size_t)width > len)
            len = width;
          ss = ALLOC_ATOM_N(char, len+1);
          sprintf(ss, temp, p);
        } else
          sprintf(ss, "%s", p);
        objc_str_stream_write(self, ss);
        break;
      case 'p':
        sprintf(s, temp, va_arg(ap, void *));
        objc_str_stream_write(self, s);
        break;
      case '%':
        objc_str_stream_writec(self, '%');
        break;
      default:
        printf("objc_str_stream_writef(): format error -- `%s'\n", f);
        exit(1);
      }
    } else
      objc_str_stream_writec(self, *f);
  }
}

